# author: hulingchuan@hotmail.com
#
# Guesser
# guess a word one by one character

require 'set'
require_relative './logger_singleton'

class Guesser

  LETTER_FREQUENCY = %w(E T A O I N S H R D L C U M W F G Y P B V K J X Q Z)

  def initialize (words, connector, word, try_limit)
    @words            = words
    @connector        = connector
    @logger           = LoggerSingleton.instance
    @used             = Set.new
    @last_result      = word
    @tried            = 0
    @limit            = try_limit
  end

  public
  def guess

    @logger.info 'guessing'

    while continue_guess
      nc = next_char
      if nc.nil?
        @logger.info 'no candidates, skip it...'
        break
      end
      @logger.info "trying #{nc}"
      @cur_result = @connector.guess_word nc
      @used.add nc
      adjust_words nc
      @tried += 1

      @logger.info "guess result #{@tried} times: #{@cur_result}"
    end
  end

  private
  def continue_guess

    star_count = @last_result.scan(/\*/).length
    remain     = @limit - @tried
    if star_count == 0
      @logger.info 'bingo!!!'
      return false
    elsif remain == 0
      @logger.info 'give up...'
      return false
    end

    true
  end

  def next_char

    char_freq = Hash.new {|h, k| h[k] = 0}
    @words.each { |word|
      char_set = Set.new
      word.each_char { |c|
        char_freq[c] += 1 unless (char_set.include?(c) or @used.include? c)
        char_set.add c
      }
    }

    max = 0
    c   = nil
    char_freq.each { |k, v|
      if max < v
        c = k
        max = v
      end
    }

    if c.nil?
      @logger.info 'no candidates, try frequent letter...'
      LETTER_FREQUENCY.each { |l|
        if @used.include? l
          next
        end
        @used.add l
        return l
      }
    end

    c
  end

  def adjust_words (c)

    res = @last_result != @cur_result

    new_words = []
    pattern   = Regexp.new @cur_result.gsub(/\*/, '[A-Z]')
    @words.each { |word|
      if res && (not pattern.match(word).nil?)
        new_words.push word
      elsif (not res) && (not word.index(c))
        new_words.push word
      end
    }

    @last_result = @cur_result
    @words       = new_words
  end

end